discord websocket tutorial for linux (sorry cygwin or other windows solution unknown atm)
the first few steps here include a way to test your server. if you'd like to skip that part
and just see if it works, look at the README_TLDR.txt instead

this WILL cause your hotboot to take a moment longer (1-2 seconds) in some cases when
it is releasing and restarting the socket. the python also waits a few seconds before
it starts listening to the socket so your messages might not go through to discord in
the first 3 seconds. you can try to tighten these timings if you want in the scripts
settings

if you have a crash where your mud hangs you may need to hotboot twice to get the
python script to restore properly. 

after you've got it working, check your processes to make sure the PyMUDDiscoServer.py
is running

you will need python3 (tested on 3.10.12 but any newer version should work as well)

copy websocket_server.cpp to an empty directory on your pc, i named my dir websocket_test

run these commands to install 'boost' and 'boost::beast':
    sudo apt-get update
    sudo apt-get install libboost-all-dev

probably already have but will also need to run:
    sudo apt-get install g++ cmake

[ THIS IS IF YOU WANT TO TEST THE CONNECTION OUTSIDE OF THE MUD ]

cd to your directory where websocket_server.cpp is:
    g++ -std=c++17 -o websocket_server websocket_server_TEST.cpp -lboost_system -lpthread

this is the command to run the server, you will need this open for the test in a moment.
run this command in the directory with the newly made websocket_server file (the same one
you ran the last command in likely) this is just to test to make sure you have a connection
and you can skip this if you're confident it will work
    ./websocket_server
    
moving over to python now, make sure you have python installed and pip:
    pip install websockets    
    
if you're not used to python and your ide can't see the import library, make sure 
your pip install put websockets in a location that your linux user can see it. it
was most likely installed locally instead of globally

make sure your websocket server is running (see above)

run the python script from your terminal:
    python3 websockettest2.py

If successful you should see in the python related terminal a couple messages:
"Connected to the WebSocket server."
"Message sent."

Where your websocket server is running you should see:
"Received message: Hello, WebSocket server!"

You can close these down now. (ctrl+c or ctrl+d is the likely command)

[ END TEST ]
    
put 'websocket_server.cpp' (not test) and 'websocket.h' in your src directory of your MUD

make sure you change the line to your path:
    result = std::system("python3 /path/to/your/src/PyMUDDiscoServer.py &");

Edit your makefile. Luckily the makefile for AFKMud is pretty magical,
just add websocket_server.cpp in the correct place with all the other
cpp files in 'C_FILES =' and it should take care of the rest

I would shutdown your server now and do a make clean to make sure it
compiles cleanly. (it did for me, so good luck here with errors)

websocket_server.o may take quite a bit of time to compile compared
to other files. but not more than a few seconds (4 core old PC for reference)

at the top of db.cpp make sure to:
    #include "websocket.h"

in your db.cpp file with all the other load_race() load_liquids() etc.
place:
   log_string( "Initializing Discord Webscoket");
   start_websocket_server();

startup the mud and if everything went okay you can start adding
send_to_discord() somewhere for the next test

make sure to #include websocket.h in any file that is going to use
this function

For example in channels.cpp I added (the +'s) to the send_tochannel function:
(more complex example below)

      if( ch->has_pcflag( PCFLAG_WIZINVIS ) )
         ch->printf( "&[%s](%d) You %s '%s'\r\n", channel->colorname.c_str(  ), ( !ch->isnpc(  ) ? ch->pcdata->wizinvis : ch->mobinvis ), channel->name.c_str(  ), argument.c_str(  ) );
      else
         ch->printf( "&W[&[%s]%s&W] %s: &w'&[%s]%s&d'\r\n", channel->colorname.c_str(  ), capitalize(channel->name).c_str(  ), ch->name, channel->colorname.c_str(  ), argument.c_str(  ) );

+     // Create a buffer for Discord
+     std::string discord_message = "[" + capitalize(channel->name) + "] " + ch->name + ": '" + argument + "'";
+     send_to_discord(discord_message);

make and hotboot

[ TEST 2 ]
run websockettest2.py - if you want to test locally - otherwise skip this step

send a chat message in the game and see if the terminal running the python script receives the message

if it comes to websockettest2.py correctly you should be good to go

[ END TEST 2]

now you'll need to set up a discord bot, open the other README_discord_bot

Enter that info into the top of PyMUDDiscoServer.py and it should monitor and 
send to whatever channel you've selected within the discord server

You must be running the python script to monitor for messages between discord and the MUD, you can
add this to your startup script if you'd like - make sure to update the path, this is what the
relevant chunk of my startup script looks like

    # Record starting time
    date > $logfile
    date > ../area/boot.txt
    # Kill any existing PyMUDDiscoServer.py process
    pkill -f PyMUDDiscoServer.py

    # Start the Python WebSocket listener
    echo "Starting Python WebSocket listener..."
    #Make sure to change this to your correct path
    python3 /path/to/your/src/PyMUDDiscoServer.py &
    
    # Run AFKMud.
    # Check if already running
    set matches = `netstat -an | grep ":$port " | grep -c LISTEN`
    if ( $matches >= 1 ) then
        # Already running
	echo Port $port is already in use.
        exit 0
    endif
    ../src/afkmud $port >&! $logfile
    
The python script has a built in delay of 3 seconds before starting up, as well as
a 3 second retry. This is because the script will run before the websocket has been
opened and throw an error. You can adjust these at the top of the Python scrip if
your MUD has a longer compile time.

add a new function under void to_channel in channels.cpp unluckily to_channel doesn't quite seem to work
for our needs

make sure to also add includes in channels.cpp:
    #include "websocket.h"
    #include <iostream>
    #include <string>

/* Function to handle broadcasting to a channel like to_channel but not restricted to log channels */
void broadcast_to_channel(mud_channel *channel, const std::string &nickname, const std::string &message) {
    if (!channel) {
        std::cerr << "Error: Null channel passed to broadcast_to_channel" << std::endl;
        return;
    }

    for (auto *d : dlist) {
        char_data *vch = d->original ? d->original : d->character;

        if (!vch || d->connected != CON_PLAYING)
            continue;

        if (vch->level >= channel->level && hasname(vch->pcdata->chan_listen, channel->name)) {
            // Use the vch->printf to format and send the message
            // All channels are the same format for now in AFKMud
            // Adds a * before the name to signify it is from Discord
            vch->printf(
                "&W[&[%s]%s&W] *%s: &w'&[%s]%s&d'\r\n",
                channel->colorname.c_str(),
                capitalize(channel->name).c_str(),
                nickname.c_str(),
                channel->colorname.c_str(),
                message.c_str()
            );
        }
    }
}

Also declare it in the channels.h file (it should already be there)
    void broadcast_to_channel(mud_channel *channel, const std::string &nickname, const std::string &message);

This is then used in the websocketserver.cpp file


I edited the send_tochannel function to handle sending discord messages. You can customize this
to fit how your MUD works.

At the top of the function add
    // Pronoun arrays based on sex
   const char *he_she[] = { "it", "he", "she", "it" };
   const char *him_her[] = { "it", "him", "her", "it" };
   const char *his_her[] = { "its", "his", "her", "its" };
   
And then I replaced the if(social) section just under bool emote = false;
You want to construct a message to send to Discord with the appropriate tags based
on the 'help variables' for socials.
    std::string discord_message; // Declare a variable to hold the message to send to Discord

   if( social ) 
   {
      // Manually expand tokens for the social case
      std::string expanded_message = socbuf_other;
         
      // Replace tokens in the expanded message manually
      size_t pos;
      while ((pos = expanded_message.find("$n")) != std::string::npos) {
         expanded_message.replace(pos, 2, ch->name);
      }
      while ((pos = expanded_message.find("$N")) != std::string::npos && victim) {
         expanded_message.replace(pos, 2, victim->name);
      }
      while ((pos = expanded_message.find("$e")) != std::string::npos) {
         expanded_message.replace(pos, 2, he_she[URANGE(0, ch->sex, SEX_MAX - 1)]);
      }
      while ((pos = expanded_message.find("$E")) != std::string::npos && victim) {
         expanded_message.replace(pos, 2, he_she[URANGE(0, victim->sex, SEX_MAX - 1)]);
      }
      while ((pos = expanded_message.find("$m")) != std::string::npos) {
         expanded_message.replace(pos, 2, him_her[URANGE(0, ch->sex, SEX_MAX - 1)]);
      }
      while ((pos = expanded_message.find("$M")) != std::string::npos && victim) {
         expanded_message.replace(pos, 2, him_her[URANGE(0, victim->sex, SEX_MAX - 1)]);
      }
      while ((pos = expanded_message.find("$s")) != std::string::npos) {
         expanded_message.replace(pos, 2, his_her[URANGE(0, ch->sex, SEX_MAX - 1)]);
      }
      while ((pos = expanded_message.find("$S")) != std::string::npos && victim) {
         expanded_message.replace(pos, 2, his_her[URANGE(0, victim->sex, SEX_MAX - 1)]);
      }

      // Construct the Discord message
      discord_message = "[" + capitalize(channel->name) + "] " + expanded_message;

      // Handle the social output for the player
      act_printf(AT_PLAIN, ch, nullptr, victim, TO_CHAR, "&W[&[%s]%s&W] &[%s]%s",
               channel->colorname.c_str(), capitalize(channel->name).c_str(),
               channel->colorname.c_str(), socbuf_char.c_str());
   }
   else if( emote ) {
      ch->printf( "&W[&[%s]%s&W] &[%s]%s %s\r\n",
                  channel->colorname.c_str(  ), capitalize( channel->name ).c_str(  ), 
                  channel->colorname.c_str(  ), ch->name, argument.c_str(  ) );
      // Construct the message for Discord (emote)
      discord_message = "[" + capitalize(channel->name) + "] " + ch->name + " " + argument;
   }
   else {
      if( ch->has_pcflag( PCFLAG_WIZINVIS ) ) {
         ch->printf( "&[%s](%d) You %s '%s'\r\n", channel->colorname.c_str(  ), 
                     (!ch->isnpc() ? ch->pcdata->wizinvis : ch->mobinvis), 
                     channel->name.c_str(  ), argument.c_str(  ) );
         // Construct the message for Discord (invisible player)
         discord_message = "[" + capitalize(channel->name) + "] " + "(Invis) " + ch->name + ": '" + argument + "'";
      } else {
         ch->printf( "&W[&[%s]%s&W] %s: &w'&[%s]%s&d'\r\n", 
                     channel->colorname.c_str(  ), capitalize(channel->name).c_str(  ), 
                     ch->name, channel->colorname.c_str(  ), argument.c_str(  ) );
         // Construct the message for Discord (regular message)
         discord_message = "[" + capitalize(channel->name) + "] " + ch->name + ": '" + argument + "'";
      }
   }

   // Check if the channel is one of those we want to send to Discord
   if (channel && !discord_message.empty()) {
      if (channel->name == "chat") {
         send_to_discord("CHAT|" + discord_message);  // Prefix the message with "CHAT"
      } else if (channel->name == "blah") {
         send_to_discord("BLAH|" + discord_message);  // Prefix the message with "INFO"
      }
   }


Fixing Hotboots to load websockets and startup

in db.cpp include:
    #include "websocket.h"
    
under web_arealist(); almost at the end of void boot_db add:
   log_string( "Starting web socket");
   start_websocket_server();
   
in hotboot.cpp include:
    #include "websocket.h"

and in CMDF(do_hootboot) add before log_string( "Executing hotboot...." );:
   log_string( "stopping websocket server" );
   shutdown_websocket_server();
   
"shutdown mud now", do a make clean, and hope it works!

please note this is set up currently to look for the chat channel, you would need
to add additional if checks to decide if other channels should be used in discord


examples: 
1)

in my addchanges file i added something like:

    std::string changebuf = changes_table[maxChanges-1].change;
    to_channel(changebuf, "slayersilent", 1);
    
and added in channels.cpp to_channel (which is for log or info type channels)
you can check for multiple channels this way if you want to use a public
channel for one and a private channel that would only post to discord

    if (channel && (channel->name == "SLAYER" || channel->name == "slayersilent")) {
      std::string discord_message = "[SLAYER] " + argument;
      std::string cleaned_message = strip_color_codes(discord_message); // Strip color codes
      send_to_discord("SLAYER|" + cleaned_message);  // Prefix the message with "SLAYER"
   }
   
2)

One way to check for multiple channels
   if (channel && !discord_message.empty()) {
      if (channel->name == "chat") {
         send_to_discord("CHAT|" + discord_message);  // Prefix the message with "CHAT"
      } else if (channel->name == "blah") {
         send_to_discord("BLAH|" + discord_message);  // Prefix the message with "INFO"
      }
   }